模拟场景：项目中不能出现晓多、xiaoduo字样

// removeXiaoduoLoader.js

module.exports = function(source) {
  // 在源代码中替换晓多和xiaoduo字样为空字符串
  const processedSource = source.replace(/晓多|xiaoduo/g, '');
  
  // 返回处理后的代码
  return processedSource;
};

<!-- loader开发原则 -->
1.单一原则，每个loader只做一件事，简单易用，易于维护。
2.链式调用：webpack会按顺序，链式调用每个loader.
3.统一原则：遵循 webpack 制定的设计规则和结构，输入与输出均为字符串，
4.无状态原则：在转换不同模块时，不应该在loader中保留状态

注意：导出的loader函数不能使用箭头函数，很多loader内部的方法都需要通过this调用
（this.cacheable()进行缓存，this.sourceMap判断是否需要生成sourceMap等）

异步loader:async/await阻塞操作执行、loader本身提供的回调函数callback

<!-- 在编写 loader 前，我们首先需要了解 loader 的本质 -->
其本质为函数，函数中的 this 作为上下文会被 webpack 填充，因此我们不能将 loader设为一个箭头函数
函数接受一个参数，为 webpack 传递给 loader 的文件源内容
函数中 this 是由 webpack 提供的对象，能够获取当前 loader 所需要的各种信息
函数中有异步操作或同步操作，异步操作通过 this.callback 返回，返回值要求为 string 或者 Buffer

代码如下所示：

// 导出一个函数，source为webpack传递给loader的文件源内容
module.exports = function(source) {
    const content = doSomeThing2JsString(source);
    
    // 如果 loader 配置了 options 对象，那么this.query将指向 options
    const options = this.query;
    
    // 可以用作解析其他模块路径的上下文
    console.log('this.context');
    
    /*
     * this.callback 参数：
     * error：Error | null，当 loader 出错时向外抛出一个 error
     * content：String | Buffer，经过 loader 编译后需要导出的内容
     * sourceMap：为方便调试生成的编译后内容的 source map
     * ast：本次编译生成的 AST 静态语法树，之后执行的 loader 可以直接使用这个 AST，进而省去重复生成 AST 的过程
     */
    this.callback(null, content); // 异步
    return content; // 同步
}
一般在编写loader的过程中，保持功能单一，避免做多种功能

如less文件转换成 css 文件也不是一步到位，而是 less-loader、css-loader、style-loader几个 loader 的链式调用才能完成转换